﻿//#define COMMANDTRACE
using System;
using System.Diagnostics;

namespace FreshMeat.Xaml
{
   /// <summary>
   /// Command that takes a parameter of type <typeparamref name="T"/>.
   /// </summary>
   /// <typeparam name="T">Type type of the parameter passed to the command.</typeparam>
   public class Command<T> : CommandBase
   {
      readonly CommandParameterConversionDelegate<T> conversionDelegate;
      readonly CommandCanExecuteDelegate<T> canExecuteDelegate;
      readonly CommandExecuteDelegate<T> executeDelegate;
      bool? previousCanExecuteResult;

      /// <summary>
      /// Creates instance using specified parameters and where the parameter is automatically converted
      /// to <typeparamref name="T"/> by a simple cast.
      /// </summary>
      /// <param name="canExecuteDelegate">Determines whether command can execute.</param>
      /// <param name="executeDelegate">Performs whatever work should be done when the command is executed.</param>
      /// <param name="context">Context which is typically shared by multiple commands so only one can execute at a time or null to create a context for use by this command.</param>
      /// <param name="name">Name for debugging purposes or null to generate a unique name.</param>
      public Command(CommandCanExecuteDelegate<T> canExecuteDelegate, CommandExecuteDelegate<T> executeDelegate, OperationContext context = null, string name = null)
         : this(canExecuteDelegate, executeDelegate, parameter => (T)parameter, context, name)
      {
      }

      /// <summary>
      /// Creates instance using specified parameters.
      /// </summary>
      /// <param name="canExecuteDelegate">Determines whether command can execute.</param>
      /// <param name="executeDelegate">Performs whatever work should be done when the command is executed.</param>
      /// <param name="conversionDelegate">Converts parameter to type <typeparamref name="T"/>.</param>
      /// <param name="context">Context which is typically shared by multiple commands so only one can execute at a time or null to create a context for use by this command.</param>
      /// <param name="name">Name for debugging purposes or null to generate a unique name.</param>
      public Command(CommandCanExecuteDelegate<T> canExecuteDelegate, CommandExecuteDelegate<T> executeDelegate, CommandParameterConversionDelegate<T> conversionDelegate, OperationContext context = null, string name = null)
         : this(canExecuteDelegate, conversionDelegate, context, name)
      {
         this.executeDelegate = executeDelegate;
      }

      /// <summary>
      /// Creates instance using specified parameters.
      /// </summary>
      /// <param name="canExecuteDelegate">Determines whether command can execute.</param>
      /// <param name="conversionDelegate">Converts parameter to type <typeparamref name="T"/>.</param>
      /// <param name="context">Context which is typically shared by multiple commands so only one can execute at a time or null to create a context for use by this command.</param>
      /// <param name="name">Name for debugging purposes or null to generate a unique name.</param>
      protected Command(CommandCanExecuteDelegate<T> canExecuteDelegate, CommandParameterConversionDelegate<T> conversionDelegate, OperationContext context = null, string name = null)
         : base(context, name)
      {
         this.conversionDelegate = conversionDelegate;
         this.canExecuteDelegate = canExecuteDelegate;
      }

      public override bool CanExecute(object parameter)
      {
         if (Context.IsExecuting)
         {
            if (!previousCanExecuteResult.HasValue || previousCanExecuteResult == true)
               Log("CanExecute returning false because '{0}' is executing.", Context.WhoIsExecuting ?? "?");
            previousCanExecuteResult = false;
            return false;
         }

         if (!canExecuteDelegate(conversionDelegate(parameter)))
         {
            if (!previousCanExecuteResult.HasValue || previousCanExecuteResult == true)
               Log("CanExecute returning false because delegate returned false (parameter={0}).", parameter ?? "null");
            previousCanExecuteResult = false;
            return false;
         }

         if (!previousCanExecuteResult.HasValue || previousCanExecuteResult == true)
            Log("CanExecute returning true (parameter={0})", parameter ?? "null");
         previousCanExecuteResult = true;
         return true;
      }

      public override void Execute(object parameter)
      {
         if (!Context.TryStart(Name))
         {
            Log("Execution prevented because Context.TryStart returned false.");
            return;
         }

         var stopwatch = Stopwatch.StartNew();

         var convertedParameter = conversionDelegate(parameter);
         Execute(convertedParameter, stopwatch);
      }

      protected virtual void Execute(T parameter, Stopwatch stopwatch)
      {
         using (Context)
            executeDelegate(parameter);

         var elapsed = stopwatch.Elapsed;
         if (elapsed < TimeSpan.FromSeconds(1))
            Log("Executed delegate ({0:F0}ms)", elapsed.TotalMilliseconds);
         else
            Log("SLOW: Executed delegate ({0:F0}ms) -- consider AysnCommand instead", elapsed.TotalMilliseconds);
      }

      [Conditional("COMMANDTRACE")]
      protected void Log(string format, params object[] args)
      {
         var prefix = string.IsNullOrEmpty(Name) ? "Command" : "Command '" + Name + "'";
         var message = string.Format(format, args);
         Trace.TraceInformation(string.Format("{0}: {1}", prefix, message));
      }
   }

   /// <summary>
   /// Simple command that doesn't use a parameter.
   /// </summary>
   public class Command : Command<object>
   {
      public Command(CommandCanExecuteDelegate canExecute, CommandExecuteDelegate execute, OperationContext context = null, string name = null)
         : base(_ => canExecute(), _ => execute(), value => value, context, name)
      {
      }
   }

   /// <summary>
   /// Delegate for function which converts the raw ICommand parameter, which is always an object,
   /// into the type expected by the <see cref="Command{T}"/>.
   /// </summary>
   public delegate T CommandParameterConversionDelegate<out T>(object parameter);

   /// <summary>
   /// Delegate for function which determines whether a <see cref="Command{T}"/> can be executed.
   /// </summary>
   /// <returns>True if command can be executed.</returns>
   public delegate bool CommandCanExecuteDelegate<in T>(T parameter);

   /// <summary>
   /// Delegate for method to be executed by a <see cref="Command{T}"/>.
   /// </summary>
   public delegate void CommandExecuteDelegate<in T>(T parameter);

   /// <summary>
   /// Delegate for function which determines whether a <see cref="Command"/> can be executed.
   /// </summary>
   /// <returns>True if command can be executed.</returns>
   public delegate bool CommandCanExecuteDelegate();

   /// <summary>
   /// Delegate for method to be executed by a <see cref="Command"/>.
   /// </summary>
   public delegate void CommandExecuteDelegate();
}
